/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers;

import com.facebook.common.internal.ImmutableMap;
import com.facebook.common.internal.VisibleForTesting;
import com.facebook.common.logging.FLog;
import com.facebook.common.memory.PooledByteBuffer;
import com.facebook.common.memory.PooledByteBufferFactory;
import com.facebook.common.memory.PooledByteBufferInputStream;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.util.UriUtil;
import com.facebook.imageformat.DefaultImageFormats;
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imageutils.BitmapUtil;
import com.facebook.imageutils.JfifUtil;
import com.facebook.soloader.DoNotOptimize;
import com.facebook.imagepipeline.annotation.Nullable;
import ohos.aafwk.ability.DataAbilityHelper;
import ohos.aafwk.ability.DataAbilityRemoteException;
import ohos.global.resource.RawFileDescriptor;
import ohos.media.image.ImageSource;
import ohos.utils.Pair;
import ohos.utils.net.Uri;

import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Executor;

/**
 * A producer that retrieves exif thumbnails.
 *
 * <p>At present, these thumbnails are retrieved on the java heap before being put into native
 * memory.
 */
public class LocalExifThumbnailProducer implements ThumbnailProducer<EncodedImage> {

    private static final int COMMON_EXIF_THUMBNAIL_MAX_DIMENSION = 512;

    public static final String PRODUCER_NAME = "LocalExifThumbnailProducer";
    @VisibleForTesting
    static final String CREATED_THUMBNAIL = "createdThumbnail";

    private final Executor mExecutor;
    private final PooledByteBufferFactory mPooledByteBufferFactory;
    private final DataAbilityHelper mContentResolver;

    public LocalExifThumbnailProducer(
            Executor executor,
            PooledByteBufferFactory pooledByteBufferFactory,
            DataAbilityHelper contentResolver) {

        mExecutor = executor;
        mPooledByteBufferFactory = pooledByteBufferFactory;
        mContentResolver = contentResolver;
    }

    /**
     * Checks whether the producer may be able to produce images of the specified size. This makes no
     * promise about being able to produce images for a particular source, only generally being able
     * to produce output of the desired resolution.
     *
     * <p>In this case, assumptions are made about the common size of EXIF thumbnails which is that
     * they may be up to 512 pixels in each dimension.
     *
     * @param resizeOptions the resize options from the current request
     * @return true if the producer can meet these needs
     */
    @Override
    public boolean canProvideImageForSize(ResizeOptions resizeOptions) {
        return ThumbnailSizeChecker.isImageBigEnough(
                COMMON_EXIF_THUMBNAIL_MAX_DIMENSION, COMMON_EXIF_THUMBNAIL_MAX_DIMENSION, resizeOptions);
    }

    @Override
    public void produceResults(
            final Consumer<EncodedImage> consumer, final ProducerContext producerContext) {

        final ProducerListener2 listener = producerContext.getProducerListener();
        final ImageRequest imageRequest = producerContext.getImageRequest();

        producerContext.putOriginExtra("local", "exif");
        final StatefulProducerRunnable cancellableProducerRunnable =
                new StatefulProducerRunnable<EncodedImage>(
                        consumer, listener, producerContext, PRODUCER_NAME) {
                    @Override
                    protected @Nullable
                    EncodedImage getResult() throws Exception {
                        final Uri sourceUri = imageRequest.getSourceUri();

                        final ImageSource exifInterface = getExifInterface(sourceUri);
                        //TODO exifInterface == null  || !exifInterface.hasThumbnail()    hasThumbnail没有替代
                        if (exifInterface == null) {
                            return null;
                        }

                        byte[] bytes = exifInterface.getImageThumbnailBytes();
                        PooledByteBuffer pooledByteBuffer = mPooledByteBufferFactory.newByteBuffer(bytes);
                        return buildEncodedImage(pooledByteBuffer, exifInterface);
                    }

                    @Override
                    protected void disposeResult(EncodedImage result) {
                        EncodedImage.closeSafely(result);
                    }

                    @Override
                    protected Map<String, String> getExtraMapOnSuccess(final EncodedImage result) {
                        return ImmutableMap.of(CREATED_THUMBNAIL, Boolean.toString(result != null));
                    }
                };
        producerContext.addCallbacks(
                new BaseProducerContextCallbacks() {
                    @Override
                    public void onCancellationRequested() {
                        cancellableProducerRunnable.cancel();
                    }
                });
        mExecutor.execute(cancellableProducerRunnable);
    }

    @VisibleForTesting
    @Nullable
    ImageSource getExifInterface(Uri uri) throws DataAbilityRemoteException {
        final String realPath = UriUtil.getRealPathFromUri(mContentResolver, uri);
        try {
            if (canReadAsFile(realPath)) {
                return ImageSource.create(realPath, new ImageSource.SourceOptions());
            } else {
                RawFileDescriptor assetFileDescriptor =
                        UriUtil.getAssetFileDescriptor(mContentResolver, uri);
                if (assetFileDescriptor != null) {
                    final ImageSource exifInterface =
                            (new Api24Utils()).getExifInterface(assetFileDescriptor.getFileDescriptor());
                    assetFileDescriptor.close();
                    return exifInterface;
                }
            }
        } catch (IOException e) {
            // If we cannot get the exif interface, return null as there is no thumbnail available
        } catch (StackOverflowError e) {
            // Device-specific issue when loading huge images
            FLog.e(LocalExifThumbnailProducer.class, "StackOverflowError in ExifInterface constructor");
        }
        return null;
    }

    private EncodedImage buildEncodedImage(PooledByteBuffer imageBytes, ImageSource exifInterface) {
        Pair<Integer, Integer> dimensions =
                BitmapUtil.decodeDimensions(new PooledByteBufferInputStream(imageBytes));
        int rotationAngle = getRotationAngle(exifInterface);
        int width = dimensions != null ? dimensions.f : EncodedImage.UNKNOWN_WIDTH;
        int height = dimensions != null ? dimensions.s : EncodedImage.UNKNOWN_HEIGHT;
        EncodedImage encodedImage;
        CloseableReference<PooledByteBuffer> closeableByteBuffer = CloseableReference.of(imageBytes);
        try {
            encodedImage = new EncodedImage(closeableByteBuffer);
        } finally {
            CloseableReference.closeSafely(closeableByteBuffer);
        }
        encodedImage.setImageFormat(DefaultImageFormats.JPEG);
        encodedImage.setRotationAngle(rotationAngle);
        encodedImage.setWidth(width);
        encodedImage.setHeight(height);
        return encodedImage;
    }

    // Gets the correction angle based on the image's orientation
    private int getRotationAngle(final ImageSource exifInterface) {
        return JfifUtil.getAutoRotateAngleFromOrientation(
                Integer.parseInt(exifInterface.getImagePropertyString("Orientation")));
    }

    @VisibleForTesting
    boolean canReadAsFile(String realPath) throws IOException {
        if (realPath == null) {
            return false;
        }
        final File file = new File(realPath);
        return file.exists() && file.canRead();
    }

    @DoNotOptimize
    private class Api24Utils {
        ImageSource getExifInterface(FileDescriptor fileDescriptor) throws IOException {
            return ImageSource.create(fileDescriptor, null);
        }
    }
}
